-- PDMG - Prop Damage by Fantym
if !SERVER then return end
AddCSLuaFile("pdmg_client.lua")
include("pdmg_convars.lua")
include("pdmg_exclusions.lua")

-- Function for adding our tag to the servers
local function AddServerTag(  tag )
	
	local sTags = GetConVarString("sv_tags")	
	
	if sTags == nil then
		RunConsoleCommand("sv_tags", tag)
	elseif  !(string.find(sTags, tag)) then
		RunConsoleCommand("sv_tags", sTags .. "," .. tag)
	end
	
end

local function validPhys(phys)
	
	if phys == nil then return false end
	if !(phys:IsValid()) then return false end
	
	return true
end

local function tblEqual( tbl1, tbl2 )

	local idxTbl1 = table.Count(tbl1)
	local idxTbl2 = table.Count(tbl2)
	
	if !(idxTbl1 == idxTbl2) then return false end
	
	for i = 1, idxTbl1 do
	
		if !( tbl1[i] == tbl2[i]) then return false end
	
	end
	
	return true

end

print("PDMG - Starting Server Side...")

pdmgLoaded = false

print("PDMG - Checking ConVars...")

-- Delay tag setting or it will fail
timer.Simple(10, AddServerTag, "pdmg")

--Load the ConVars into variables here so they are global without polling the value each time.

local cvEnabled, cvAutoHeal, cvAutoHealInterval, cvAutoHealPlayers
local cvAutoHealPlayersInterval, cvAutoHealNPCs, cvAutoHealNPCsInterval, cvHealthOverride
local cvUseMass, cvUseVolume, cvHealthRagdoll, cvConstraintRemove, cvConstraintPercent
local cvUnfreeze, cvUnfreezePercent, cvColorDamage, cvPropsOnly, cvHealthMultiplier
local cvExWire, cvExStargate, cvExGmod, cvExPhxExp, cvExHl2Exp

function UpdateConVars()
	 cvEnabled = GetConVarNumber("pdmg_enabled")
	 cvAutoHeal = GetConVarNumber("pdmg_autoheal")
	 cvAutoHealInterval = GetConVarNumber("pdmg_autoheal_interval")
	 cvAutoHealPlayers = GetConVarNumber("pdmg_autoheal_players")
	 cvAutoHealPlayersInterval = GetConVarNumber("pdmg_autoheal_players_interval")
	 cvAutoHealNPCs = GetConVarNumber("pdmg_autoheal_npcs")
	 cvAutoHealNPCsInterval = GetConVarNumber("pdmg_autoheal_npcs_interval")
	 cvHealthOverride = GetConVarNumber("pdmg_override_health") 
	 cvUseMass = GetConVarNumber("pdmg_use_mass")
	 cvUseVolume = GetConVarNumber("pdmg_use_volume")
	 cvHealthRagdoll = GetConVarNumber("pdmg_ragdoll_health")
	 cvConstraintRemove = GetConVarNumber("pdmg_breakconstraints")
	 cvConstraintPercent = GetConVarNumber("pdmg_breakconstraints_percent")
	 cvUnfreeze = GetConVarNumber("pdmg_unfreeze")
	 cvUnfreezePercent = GetConVarNumber("pdmg_unfreeze_percent")
	 cvColorDamage = GetConVarNumber("pdmg_color_damage")
	 cvPropsOnly = GetConVarNumber("pdmg_props_only")
	 cvHealthMultiplier = GetConVarNumber("pdmg_health_multiplier")
	 cvExWire = GetConVarNumber("pdmg_ex_wire")
	 cvExStargate = GetConVarNumber("pdmg_ex_stargate")
	 cvExGmod = GetConVarNumber("pdmg_ex_gmod")
	 cvExPhxExp = GetConVarNumber("pdmg_ex_phx_exp")
	 cvExHl2Exp = GetConVarNumber("pdmg_ex_hl2_exp")
end
UpdateConVars()

--Timer Counter for Entity Spawning
local timerTicker = 0

--Initialize Ent Table
local pdmgEnts = {}

--Function To Check Partial Class names
--ex. IsClass( ent, "npc_" ) would return all ents with classes starting with npc_
-- if you want all the antlions IsClass(ent, "npc_a")fs
local function IsClass( ent, class )
	if (string.find(ent:GetClass(), class) == nil) then return false else return true end
end

local function IsExcluded(ent)

	local i = table.Count(pdmgExclusions)
	
	for n = 1, i do
		
		if (ent:GetModel() == pdmgExclusions[n]) or 
		   (ent:GetClass() == pdmgExclusions[n]) then
				--print("Entity " .. tostring(ent) .. " is excluded from damage.")
				return true
		end
	end
	
	--print("Entity " .. tostring(ent) .. " is not excluded from damage.")
	return false
	
end

--Function for Health Creation
local function DoHealth( v )
	
	if (GetConVarNumber("pdmg_enabled") == 0) then return end
	
	-- If it doesn't have a model we can't kill it
	if v:GetModel() == nil then return end
	
	-- Prop_physics only option
	if cvPropsOnly == 1 then
		if !(IsClass(v, "prop_physics")) and
		   (v.IsPlayer and !v:IsPlayer()) then 
			v.HealthMade = nil
			if v.GetMaxHealth then v.org_health = v:GetMaxHealth() end
			v:SetMaxHealth(-1)
			v:SetHealth(-1)
			return 
		end
	end	
	
	--Class and Model Exclusion Code
	if IsExcluded(v) then 
		v.HealthMade = nil
		if v.GetMaxHealth then v.org_health = v:GetMaxHealth() end
		--v:SetSaveValue("m_takedamage", 0)
		v:SetMaxHealth(-1)
		v:SetHealth(-1)
		return 
	end
	
	if v:IsPlayer() and 
	   !table.HasValue(pdmgEnts, v) then 
	    v.HealthMade = 1
		table.insert(pdmgEnts, v)
	end
	
	--If we've done this then exit
	if !(v.HealthMade == nil) then return end
	
	-- Trying to stop double debris
	if v:Health() > 0 then v.hadHealth = true end
	
	-- Don't do health on map elements

	if IsClass( v, "func_") and !IsClass(v, "door") then return end
	
	if IsClass( v, "door") then
		v:SetSaveValue("m_takedamage",1)
		v:SetHealth(math.random(100,200))
		v:SetMaxHealth(v:Health())
	end
	
	local vPhys = v:GetPhysicsObject()
	
	-- Get Color of ent for possible reset later
	v.orgColor = {}
	v.orgColor.r, v.orgColor.g, v.orgColor.b, v.orgColor.a = v:GetColor()
	
		
	-- Somethings are breakable to start with.
	if ((v:Health() > 0) and ( cvHealthOverride == 0)) or
	   (v:IsNPC()) then
		
		if cvHealthMultiplier > 0 and !v:IsPlayer() then 
			v:SetMaxHealth(v:Health() * cvHealthMultiplier)
		else
			v:SetMaxHealth(v:Health())
		end
		
		v:SetHealth(v:GetMaxHealth())
		
		v.HealthMade = 1
		v:SetSaveValue("m_takedamage",2)

		--Add Entity to Table for healing / other stuff
		table.insert(pdmgEnts, v)
	
	elseif (v:IsValid()) and
			!(vPhys == nil) and
		   (vPhys:IsValid()) and 
		  !(v:IsWorld()) and 
		  !(v:IsPlayer()) and
		  !(v.HealthMade == 1) and
		   ((v:Health() < 1) or (cvHealthOverride > 0)) then

			--Multiplyer based off physical materials
			--     See Function SetHealthMult
			local Mult = SetHealthMult(vPhys)
			local Mass = 0
			local Vol = 0
				
			--Use mass in health calc? if no set mass = 1
			if cvUseMass == 1 then
				Mass = vPhys:GetMass()
			else
				Mass = 1
			end
				
			--Use volume in health calc? if no set volume = 1
			if cvUseVolume == 1 then
				Vol = vPhys:GetVolume()
			else
				Vol = 1
			end
							
			if Mass == 1 and Vol == 1 then 
				-- If we aren't using mass and we're  not using volume, then we must be random
				-- Set the "Mass" to a random number roughly based around the object's mass
				Mass = (math.random() * (math.random()+1.269 * vPhys:GetMass())) / (math.random() * 4)
			end 
			if Vol == nil then Vol = 420 end
			if Mass == nil then Mass = 420 end
				
			if cvHealthOverride == 0 then 
				--Standard Health calculation if we are not overriding the health.
				-- With Health Multiplier code
				if cvHealthMultiplier > 0 then 
					v:SetHealth(((Mass + (Vol * 0.002)) * Mult) * cvHealthMultiplier)
				else
					v:SetHealth((Mass + (Vol * 0.002)) * Mult)
				end
			else
				v:SetHealth(cvHealthOverride)
			end 
			
			-- Ragdolls die too easy (Beat them selves up), should we override?
			if (cvHealthRagdoll > 0) then
				if (v:GetClass() == "prop_ragdoll") then v:SetHealth(cvHealthRagdoll) end
			end
				
			--Set the entities max health and the variable that let's us know we've already done all this
			v:SetMaxHealth(v:Health())
			
			v.HealthMade = 1
			v:SetSaveValue("m_takedamage",2)
			
			--Add Entity to Table for healing / other stuff
			table.insert(pdmgEnts, v)
			
		--end
	end
end



print("PDMG - Setting Up Ents...")

-- Get all currently spawned entities
local allEnts = ents.GetAll()

--See if any qualify for health
for k, v in pairs(allEnts) do
	DoHealth(v)
end

local retardThink = 0.5
local nextThink = CurTime() + retardThink

--Main Think Function
local function PropDamage( )
		
	if CurTime() <= nextThink then return end
	
	if pdmgLoaded ==  false then return end
	-- Check for a change in the enabled convar
	
	if !(cvEnabled == GetConVarNumber("pdmg_enabled")) then
		-- If we are going from disabled to enabled we need to set health on anything we haven't yet.
		if cvEnabled == 0 and GetConVarNumber("pdmg_enabled") == 1 then
			for k, v in pairs(ents.GetAll()) do
				DoHealth( v ) 
			end
		end
		-- Need to fix color on props if we are disabling the damage
		if cvEnabled == 1 and GetConVarNumber("pdmg_enabled") == 0 then
			for k, v in pairs(pdmgEnts) do
				if !(v.orgColor == nil) then
					v:SetColor(v.orgColor.r,v.orgColor.g,v.orgColor.b,v.orgColor.a)
				end
			end
		end
		--cvEnabled = GetConVarNumber("pdmg_enabled")
	end
	
	
	-- Check Exclusions
	local updateEx = false
	local updatePo = false
	
	if !(cvPropsOnly == GetConVarNumber("pdmg_props_only")) then updatePo = true end 
	if !(cvExWire == GetConVarNumber("pdmg_ex_wire")) then updateEx = true end
	if !(cvExStargate == GetConVarNumber("pdmg_ex_stargate")) then updateEx = true end
	if !(cvExGmod == GetConVarNumber("pdmg_ex_gmod")) then updateEx = true end
	if !(cvExPhxExp == GetConVarNumber("pdmg_ex_phx_exp")) then updateEx = true end
	if !(cvExHl2Exp == GetConVarNumber("pdmg_ex_hl2_exp")) then updateEx = true end
	
	
	UpdateConVars()
	
	
	-- If Not Enabled, then exit
	if (GetConVarNumber("pdmg_enabled") == 0) then return end
	
	if updateEx then 
		--print("Rebuilding Exclusion Table") 
		BuildExTable(cvExWire, cvExStargate, cvExGmod, cvExPhxExp, cvExHl2Exp)
		-- Get all currently spawned entities
	end
	if updateEx or updatePo then
		
		---print("Updating entity health info")
		local allEnts = ents.GetAll()
		
		--See if any qualify for health
		for k, v in pairs(allEnts) do
			v.HealthMade = nil
			DoHealth(v)
		end
	end
	
	-- Run through Entity table to see if anything needs to be done
	for k, v in pairs(pdmgEnts) do
		
		--Does the entity still exist? no, then remove
		if !ValidEntity(v) then 
		
			table.remove(pdmgEnts, k)
		
		else 
		
			local vClass = v:GetClass()
			local vPhys = v:GetPhysicsObject()
			
			--Auto Heal Code
			if (v.HealthMade == 1) and (v:Health() < v:GetMaxHealth()) then
								
				--AutoHeal Props
				if (cvAutoHeal > 0) and (!(v:IsNPC()) and !(v:IsPlayer())) then
					
					if !(v.LastHeal == nil) and
					   ((CurTime() - v.LastHeal) > cvAutoHealInterval) then
						v:SetHealth(v:Health() + cvAutoHeal)
						v.LastHeal = CurTime()
					elseif v.LastHeal == nil then
						v.LastHeal = CurTime()
					end
					
				end 
				
				--AutoHeal Players
				
				if (cvAutoHealPlayers > 0) and (v:IsPlayer()) then
					
					if !(v.LastHeal == nil) and
					   ((CurTime() - v.LastHeal) >= cvAutoHealPlayersInterval) then
						v:SetHealth(v:Health() + cvAutoHealPlayers)
						v.LastHeal = CurTime()
					elseif v.LastHeal == nil then
						v.LastHeal = CurTime()
					end
					
				end
				
				--AutoHeal NPCs
				if (cvAutoHealNPCs) > 0 and (v:IsNPC()) then
					
					if !(v.LastHeal == nil) and
					   ((CurTime() - v.LastHeal) > cvAutoHealNPCsInterval) then
						v:SetHealth(v:Health() + cvAutoHealNPCs)
						v.LastHeal = CurTime()
					elseif v.LastHeal == nil then
						v.LastHeal = CurTime()
					end
					
				end
			end	

			--Color code
			
			cvColorDamage = GetConVarNumber("pdmg_color_damage")
			
			if !(v:IsNPC() or v:IsPlayer()) and ( cvColorDamage == 1 ) and (v.HealthMade == 1 ) then
				
				local clrPercent = 1 - (v:Health() / v:GetMaxHealth())

				local curColor = {}
				curColor.r, curColor.g, curColor.b, curColor.a = v:GetColor()
				
				if v.LastHealth == nil then v.LastHealth = v:Health() end
				
				if !(v.LastHealth == v:Health()) then
					local newColor = {}
					newColor.r = v.orgColor.r - (v.orgColor.r - curColor.r)
					newColor.g = v.orgColor.g - (v.orgColor.g * clrPercent)
					newColor.b = v.orgColor.b - (v.orgColor.b * clrPercent)
					newColor.a = v.orgColor.a - (v.orgColor.a - curColor.a)
					
					v:SetColor( newColor.r, newColor.g, newColor.b, newColor.a )
					v.LastHealth = v:Health()
				end
			end
			
			if (cvColorDamage == 0) then
			
				if !v.orgColor == nil then
					v:SetColor( v.orgColor.r, v.orgColor.g, v.orgColor.b, v.orgColor.a)
				end
				
			end
			
			--Health Cap -- No Cheating
			if (v:Health() > v:GetMaxHealth()) then v:SetHealth(v:GetMaxHealth()) end 
	
			-- Constraint Removal Code
			if (cvConstraintRemove == 1) and 
			   ((v:Health() / v:GetMaxHealth()) <= cvConstraintPercent) and
			   (v.HealthMade == 1 ) then
				--print("Removing Constraints")
				if v:Health() > 0 then 
					if v:IsConstrained() then constraint.RemoveAll(v) end
				end 
			end
			
			-- Unfreeze Code
			if (cvUnfreeze == 1) and
			   ((v:Health() / v:GetMaxHealth()) <= cvUnfreezePercent) and
			   (v.HealthMade == 1 ) then
				if vPhys:IsValid() then
					vPhys:EnableMotion(true)
					vPhys:Wake()
				end
			end
			
			--Uncomment the code below to get a running display of prop status'	
			--[[
			if (v.HealthMade == 1) and
			   !(v:IsWorld()) and
			   !(v:IsPlayer()) and
				(vPhys:IsValid()) then 		
				print(tostring(v) .." is " ..(vPhys:GetMaterial()) .." and has " ..(v:Health()) .." out of " ..(v:GetMaxHealth()) .." Health. ")	
			end 
			--]]
		
		end
	end
end
hook.Add( "Tick", "PropDamageTick", PropDamage )

-- Checks Entities as they spawn to see if they need Health added.
function ObjectSpawned( ent )
	
	if (GetConVarNumber("pdmg_enabled") == 0) then return end
	
	--Counter for Unique timer names.
	timerTicker = timerTicker + 1
	
	--Delay health creation, object doesn't fully exist yet.
	timer.Create("timer" .. tostring(timerTicker), 0.05 , 1, function() if (ent:IsValid()) then DoHealth(ent) end end )
	
	-- guess this doesn't go over 9000, lol
	if timerTicker > 9000 then timerTicker = 0 end
	
end
hook.Add( "OnEntityCreated", "pdmgObjectSpawned", ObjectSpawned )

--Function to gather and send prop Information
function PropDamageHud()
	
	if (GetConVarNumber("pdmg_enabled") == 0) then return end
	
	--Grab Convar
	local cvShowHealth = GetConVarNumber("pdmg_showhealth")
	
	-- If showing health is not enabled don't show health
	if cvShowHealth == 0 then 
		umsg.Start("pdmgEntityHook", j)
			umsg.Bool( false )
		umsg.End()
		return 
	end
	
	local ShowHud = false
	
	-- Get all the people
	local people = player.GetHumans()

	-- Loop and trace
	for i, j in pairs(people) do
		ShowHud = false
		
		--Show Only when using healing swep
		if ValidEntity(j:GetActiveWeapon())then
			--print(tostring(j:GetActiveWeapon()))
			if (j:GetActiveWeapon():GetClass() == "weapon_propheal" and
				cvShowHealth == 2 )  then
				ShowHud = true
			end
		end
		
		--Show when looking at
		if cvShowHealth == 1 then
		   ShowHud = true		
		   -- No Show
		end
		
		
		if !ShowHud then
		   umsg.Start("pdmgEntityHook", j)
				umsg.Bool( false )
		   umsg.End()
		end
		
		-- Ok So if we should show it, then get the rest of the info
		if ShowHud then

			--Trace from player
			local eyetrace = j:GetEyeTrace();
			local traceEnt = eyetrace.Entity
			
			--Send The trace result to the Client, and if it was send Health, maxhealth, and OBBCenter
			umsg.Start("pdmgEntityHook", j)
				umsg.Bool( traceEnt:IsValid() and !(traceEnt:IsWorld()) and (traceEnt:GetPos():Distance(j:GetPos()) < 1024 ))
		
				if traceEnt:IsValid() and 
				   traceEnt:Health() > 0 and
				   !(traceEnt:IsWorld()) then
		
					umsg.Long( traceEnt:Health() )
					umsg.Long( traceEnt:GetMaxHealth() )
					umsg.Vector( traceEnt:LocalToWorld(traceEnt:OBBCenter()) )
					
				end
			
			umsg.End()
		end		

		
	end
end
hook.Add( "Tick", "PropDamageHudTick", PropDamageHud )

--Function To try and offset health based on physics material type
function SetHealthMult(vPhys)
	
	local MaterialType = vPhys:GetMaterial()
	
	-- Values are made up, but seem to simulate different material strenth and weakness.
	if (MaterialType == "metal") then
		return 3
	elseif (MaterialType == "metalpanel") then
		return 2.25
	elseif (MaterialType == "concrete") then
		return 2
	elseif (MaterialType == "tile") then
		return 1
	elseif (MaterialType == "wood") then
		return 0.75
	elseif (MaterialType == "plastic") then
		return 1.75
	elseif (MaterialType == "plastic_barrel") then
		return 2.5
	elseif (MaterialType == "canister") then
		return 0.1
	else
		return 1
	end 

end



local function EntityTakeDamage( ent, inflictor, attacker, amount, dmginfo )

	if (GetConVarNumber("pdmg_enabled") == 0) then return end
	
	--if it was the player or the world, don't bother just exit
	--NPC handle their own damage also.
	
	--print("Entity (".. tostring(ent) ..") Took " .. tostring(dmginfo:GetDamage()) .. " damage.")
	--print("Damage Type: " .. tostring(dmginfo:GetDamageType()))
	
	if (ent:IsNPC()) or (ent:IsPlayer()) or (ent:IsWorld()) then return end
		
	if ent.HealthMade == 1 and !IsExcluded(ent) then	
	
		
		--Apply physics damage
		ent:TakePhysicsDamage(dmginfo) -- React physically when getting shot/blown
		--Get Total Damage
		local TotalDamage = dmginfo:GetDamage()
		
		--print("Entity (".. tostring(ent) ..") Took " .. tostring(TotalDamage) .. " damage.")
		
		-- uncomment below to multiply explosion damage
		--if dmginfo:IsExplosionDamage() then TotalDamage = (TotalDamage + (dmginfo:GetDamageForce():Length() * 0.01)) end
		
		--Apply the hurt.
		ent:SetHealth(ent:Health() - TotalDamage) -- Damage Entity Accordingly
		
		-- See if we killed it.
		if(ent:Health() <= 0) then
			
			--if IsClass(ent, "func_door") then
			
			if IsClass(ent, "func_door") then
				ent:SetNoDraw(true)
				--Spawn Debris
				ent:SetCollisionGroup(COLLISION_GROUP_NONE)
				ent:SetPos(Vector(0,0,15000))
				timer.Simple(0.5, function(ent) ent:SetSaveValue("m_MoveType", MOVETYPE_NONE) end, ent)
			else
			
				ent.Gibbed = ent.Gibbed or false
				--If it has gib animations, call the break code, if not this doesn't do anything
				if !ent.Gibbed and !ent.hadHealth then
					ent:GibBreakClient(ent:GetPos())
					ent.Gibbed = true
				end
				
				-- Is there an effect set for destruction?
				cvDeathEffect = GetConVarString("pdmg_death_effect")
				
				if !(cvDeathEffect == "") then
					local effect = EffectData()
				
					--setup and do effect
					if validPhys(ent:GetPhysicsObject()) then
						effect:SetStart(ent:LocalToWorld(ent:GetPhysicsObject():GetMassCenter()))
						effect:SetRadius(ent:BoundingRadius())
						effect:SetOrigin(ent:LocalToWorld(ent:GetPhysicsObject():GetMassCenter()))
						effect:SetScale(ent:GetMaxHealth())
						effect:SetMagnitude(ent:GetMaxHealth())
					else
						effect:SetStart(ent:GetPos())
						effect:SetRadius(ent:BoundingRadius())
						effect:SetOrigin(ent:GetPos())
						effect:SetScale(ent:GetMaxHealth())
						effect:SetMagnitude(ent:GetMaxHealth())
					end
					util.Effect(cvDeathEffect, effect)
				
				end
				
				--Destroy the ent.
				hook.Call("PropKilled", GAMEMODE, ent, inflictor, attacker)
				SafeRemoveEntityDelayed(ent, 0)
			end
			
			
		end

	end
	--return false
end
hook.Add( "EntityTakeDamage", "PropDamageTakeDamage", EntityTakeDamage )

print("PDMG - Building Exclusions Table...")

BuildExTable(cvExWire, cvExStargate, cvExGmod, cvExPhxExp, cvExHl2Exp)
		
--exposing dohealth
function pdmg_dohealth(ent)
	DoHealth(ent)
end

print("PDMG - Finished Loading Server!")

pdmgLoaded = true

print("... -=- ...")